const {ccclass, property,executeInEditMode} = cc._decorator;
let focus = cc.v2(0, 0)

const lerp = function(a, b, t) {
    return cc.misc.lerp(a, b, t)
}

// 相机目标类
// @property target 目标节点，一般为主角，或者敌人
// @property offset 相机焦点相对于目标节点的偏移量。也可以不设置target，只用一个offset表示固定位置
@ccclass("targetAndFocusPoint")

export class targetAndFocusPoint {
    @property({type:cc.Node})
    public target:cc.Node;

    @property()
    public offset:cc.Vec2 = cc.v2(0, 0);

    @property({type:cc.Float})
    public weight:number = 1;
    private _focus = cc.v2(0, 0)
    public get focus() {
        if (this.target) {
            this._focus.set(cc.v2(this.target.x + this.offset.x, this.target.y + this.offset.y))
        } else {
            this._focus.set(this.offset)
        }
        return this._focus;
    }

    @property()
    private sizeOffset:cc.Size = new cc.Size(0, 0);

    public getRect() {
        let width = this.target ? this.target.width : 0
        let height = this.target ? this.target.height : 0
        width += this.sizeOffset.width
        height += this.sizeOffset.height
        return new cc.Rect(
            this.focus.x - width / 2, 
            this.focus.y - height / 2, 
            width, height)
    }
}

// 是否使用摄像机窗格
const cameraVisible = function() {
    return this.useCameraRect
}

// 是否对摄像机窗格插值
const cameraLerpVisible = function() {
    return this.useCameraRect && this.useLerpCamera
}

// 是否使用硬性边缘
const edgeVisible = function() {
    return this.useEdge
}

const platformVisible = function() {
    return this.usePlatformSnapping
}

const positionLockingVisible = function() {
    return this.usePositionLocking
}

const lerpVisible = function() {
    return this.usePositionLocking && this.useLerpPosition
}

// 枚举，固定位置可以分横纵方向控制
enum PositionLockingDir {
    Horizontal,
    Vertical,
    Both
}
cc.Enum(PositionLockingDir)
@ccclass('camera2dHelp')
@executeInEditMode
export class camera2dHelp extends cc.Component {
    public static inst:camera2dHelp;
    // 相机跟随目标
    // 最简单的固定跟随主角，只需要设置一个目标即可。
    // 多个目标的情况下，可以用权重来决定各个目标的位置影响比例
    @property({type:[targetAndFocusPoint]})
    public targets:targetAndFocusPoint[] = [];

    @property
    public usePositionLocking:boolean = false;
    @property({type:PositionLockingDir, visible:positionLockingVisible})
    public positionLockingDir:PositionLockingDir = PositionLockingDir.Both;
    @property({visible:positionLockingVisible})
    public useLerpPosition:boolean = false;
    // 固定位置平滑过渡参数
    @property({type:cc.Float, range:[0, 1], step:0.01, visible:lerpVisible})
    public lerpRatio:number = 0.1;

    @property
    public useEdge:boolean = false;
    // 四个方向的硬性边界
    @property({type:cc.Float, visible:edgeVisible})
    public leftEdge:number = -99999;
    @property({type:cc.Float, visible:edgeVisible})
    public rightEdge:number = 99999;
    @property({type:cc.Float, visible:edgeVisible})
    public topEdge:number = 99999;
    @property({type:cc.Float, visible:edgeVisible})
    public bottomEdge:number = -99999;

    // 摄像机窗格
    @property
    public useCameraRect:boolean = false
    @property
    public useLerpCamera:boolean = false
    @property({type:cc.Float, range:[0, 1], step:0.01, visible:cameraLerpVisible})
    public cameraLerpRatio:number = 0.1

    camera_graphics:cc.Graphics = null

    @property
    private _rect:cc.Rect = new cc.Rect(-300,-300,600,600)
    @property({visible:cameraVisible})
    public get cameraRect () {
        return this._rect
    }
    public set cameraRect(value) {
        this._rect = value
        // 修改debug辅助线
        this.updateRectLines()
    }

    @property
    public usePlatformSnapping:boolean = false;
    public platformHeight:number = 0;
    // 使用平台高度时，镜头对齐平台的偏移量
    @property({type:cc.Float, visible:platformVisible})
    public platformOffset:number = -100;
    // 角色接触到平台时暂时激活，离开平台后熄火
    // 注意该参数和usePlatformSnapping的区别，usePlatformSnapping是使用平台捕捉，该参数是触碰和离开平台时控制平台捕捉是否激活
    public platformActive:boolean = false;
    @property({type:cc.Float, range:[0, 1], step:0.01, visible:platformVisible})
    public platformLerpRatio:number = 0.1;

        // 缩放自适应开关
    // 当有多个目标时，例如主角和boss，可以开启缩放自适应，保证主角跑远后相机能通过缩放适配，让主角和boss都时刻在视野内
    @property
    public zoomToFit:boolean = false

    protected onLoad(): void {
        this.camera_graphics = this.node.getComponent(cc.Graphics)
    }

    start() {
        camera2dHelp.inst = this
        this.updateRectLines()
    }

    public setPlatformHeight(height:number) {
        this.platformHeight = height
        this.platformActive = true
    }

    // 摄像机窗格的线条，仅用于测试显示
    private updateRectLines() {
        // 为了让显示出来的线条刚好在对象的边界处，设定一个targetSize，仅用于辅助显示摄像机窗格，实际无用
        

        if (CC_EDITOR) {
            console.log("updateRectLines")
            this.camera_graphics = this.node.getComponent(cc.Graphics)
            // 使用cc.graphics来绘制线条
            this.camera_graphics.clear()
            this.camera_graphics.strokeColor = cc.Color.RED
            this.camera_graphics.lineWidth = 2
            this.camera_graphics.rect(this._rect.x, this._rect.y, this._rect.width, this._rect.height)
            this.camera_graphics.stroke()
        }
    }

    // 摄像机窗格计算
    calculateCameraRect(p:cc.Vec2, dt) {
        let xx = p.x
        let yy = p.y
        if (focus.x <= this.node.position.x + this._rect.xMin ) {
            xx = focus.x - this._rect.xMin
        } else if (focus.x >= this.node.position.x + this._rect.xMax) {
            xx = focus.x - this._rect.xMax
        }

        if (focus.y <= this.node.position.y + this._rect.yMin) {
            yy = focus.y - this._rect.yMin
        } else if (focus.y >= this.node.position.y + this._rect.yMax) {
            yy = focus.y - this._rect.yMax
        }
        
        if (this.useLerpCamera) {
            p.x = lerp(p.x, xx, this.cameraLerpRatio)
            p.y = lerp(p.y, yy, this.cameraLerpRatio)       
        } else {
            p.set(cc.v2(xx, yy))
        }
    }

    calculateEdge(p:cc.Vec2, dt:number) {
        // 边缘限制
        let xMax = this.rightEdge - cc.visibleRect.width / 2
        let xMin = this.leftEdge + cc.visibleRect.width / 2
        let yMax = this.topEdge - cc.visibleRect.height / 2
        let yMin = this.bottomEdge + cc.visibleRect.height / 2
        p.x = Math.min(Math.max(p.x, xMin), xMax)
        p.y = Math.min(Math.max(p.y, yMin), yMax)
    }

    // 固定位置跟随计算
    calculatePositionLocking(p, focus) {
        let ratio = this.useLerpPosition ? this.lerpRatio : 1
        if (this.positionLockingDir == PositionLockingDir.Both || this.positionLockingDir == PositionLockingDir.Horizontal) {
            p.x = lerp(p.x, focus.x, ratio)
        }
        if (this.positionLockingDir == PositionLockingDir.Both || this.positionLockingDir == PositionLockingDir.Vertical) {
            p.y = lerp(p.y, focus.y, ratio)
        }
    }

    update(dt: number) {
        // 目标坐标+偏移量=焦点
        focus.set(cc.v2(0, 0))
        let weights = this.targets.length == 0 ? 1: 0;
        let min_x = Infinity
        let max_x = -Infinity
        let min_y = Infinity
        let max_y = -Infinity
        // 遍历所有目标，根据权重，计算出最终焦点
        for(let i = 0; i < this.targets.length; i++) {
            let tf = this.targets[i];
            focus.x += tf.focus.x * tf.weight;
            focus.y += tf.focus.y * tf.weight;
            weights += tf.weight;

            let rect = tf.getRect()
            min_x = Math.min(min_x, rect.xMin)
            max_x = Math.max(max_x, rect.xMax)
            min_y = Math.min(min_y, rect.yMin)
            max_y = Math.max(max_y, rect.yMax)
        }
        focus.x /= weights;
        focus.y /= weights;
        let p = cc.v2(this.node.position.x, this.node.position.y)
        if (this.usePositionLocking) {
            this.calculatePositionLocking(p, focus)
        }

        // 摄像机方格
        if (this.useCameraRect) {
            this.calculateCameraRect(p, dt)
        }

        if (this.useEdge) {
            this.calculateEdge(p, dt)
        }

        // 平台高度
        if (this.platformActive) {
            p.y = lerp(p.y, this.platformHeight - this.platformOffset, this.platformLerpRatio)
        }

        if (this.zoomToFit) {
            let halfW = Math.max(max_x - p.x, p.x - min_x);
            let xRatio = halfW * 2 / cc.visibleRect.width

            let halfH = Math.max(max_y - p.y, p.y - min_y);
            let yRatio = halfH * 2 / cc.visibleRect.height

            let r = Math.max(xRatio, yRatio)
            r = Math.max(1, r)
            this.getComponent(cc.Camera).zoomRatio = 1 / r
        }

        this.node.setPosition(cc.v3(p.x, p.y,1000))  
        this.node.emit("cameraUpdatePosition")
    }
}


